#include <sys/syscall.h>
#include <mips/regdef.h>

#define MYMALLOC_SIGNATURE 0xdeadbeef

#ifndef PROT_READ
#define PROT_READ 0x01
#endif

#ifndef PROT_WRITE
#define PROT_WRITE 0x02
#endif

#ifndef MAP_PRIVATE
#define MAP_PRIVATE 0x02
#endif

#ifndef MAP_ANON
#define MAP_ANON 0x1000
#endif

#define MALLOC_SS			56

#define MALLOC_O_ARG 		(MALLOC_SS)

#define MALLOC_O_RA  		48
#define MALLOC_O_FP			44
#define MALLOC_O_GP			40

#define MALLOC_LTA_SIZE		36	
#define MALLOC_LTA_RVAL		32 

#define MALLOC_ABA_7		28
#define MALLOC_ABA_6		24

#define MALLOC_ABA_5		16



	.text
	.align	2
	.globl	mymalloc
	.ent	mymalloc
mymalloc:
	subu	sp, sp, MALLOC_SS 
	sw	ra, MALLOC_O_RA(sp)
	sw	$fp,MALLOC_O_FP(sp)
	sw	gp, MALLOC_O_GP(sp)

	sw	a0, MALLOC_O_ARG(sp)  # Temporary: original allocation size.

	sw	a0, MALLOC_LTA_SIZE(sp)  # Temporary: actual allocation size.
	li	t0, -1
	sw	t0, MALLOC_LTA_RVAL(sp)  # Temporary: return value (defaults to -1).

	move	$fp, sp

	# Adjust the original allocation size to a 4-byte boundary.
	#
	lw	t0, MALLOC_O_ARG(sp)
	addiu	t0, t0, 3
	and	t0, t0, 0xfffffffc
	sw	t0, MALLOC_O_ARG(sp)

	# Increment the allocation size by 12 units, in order to 
	# make room for the allocation signature, block size and
	# trailer information.
	#
	lw	t0, MALLOC_O_ARG(sp)
	addiu	t0, t0, 12
	sw	t0, MALLOC_LTA_SIZE(sp)

	# mmap(0, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0)
	#
	li	v0, SYS_mmap
	li	a0, 0
	lw	a1, MALLOC_LTA_SIZE(sp)
	li	a2, PROT_READ|PROT_WRITE
	li	a3, MAP_PRIVATE|MAP_ANON

	# According to mmap(2), the file descriptor 
	# must be specified as -1 when using MAP_ANON.
	#
	li	t0, -1
	sw	t0,MALLOC_ABA_5(sp)

	# Use a trivial offset.
	#
	li	t0, 0
	sw	t0,MALLOC_ABA_6(sp)
	sw	t0,MALLOC_ABA_7(sp)


	# Excecute the syscall, save the return value.
	#
	syscall
	sw	v0, MALLOC_LTA_RVAL(sp)
	beqz	v0, mymalloc_return

	# Success. Check out the allocated pointer.
	#
	lw	t0, MALLOC_LTA_RVAL(sp)
	li	t1, MYMALLOC_SIGNATURE
	sw	t1, 0(t0)

	# The actual allocation size goes right after the signature.
	#
	lw	t0, MALLOC_LTA_RVAL(sp)
	lw	t1, MALLOC_LTA_SIZE(sp)
	sw	t1,  4(t0)

	# Trailer information.
	#
	lw	t0, MALLOC_LTA_SIZE(sp) # t0: actual allocation size.
	lw	t1, MALLOC_LTA_RVAL(sp) # t1: Pointer.
	addu	t1, t1, t0 # t1 now points to the trailing 4-byte area.
	xor	t2, t0, MYMALLOC_SIGNATURE
	sw	t2, -4(t1)

	# Increment the result pointer.
	#
	lw	t0, MALLOC_LTA_RVAL(sp)
	addiu	t0, t0, 8
	sw	t0, MALLOC_LTA_RVAL(sp)

mymalloc_return:
	# Restore the return value.
	#
	lw	v0, MALLOC_LTA_RVAL(sp)

	# Destroy the stack frame.
	#
	move	sp, $fp
	lw	ra, MALLOC_O_RA(sp)
	lw	$fp, MALLOC_O_FP(sp)
	addu	sp, sp,MALLOC_SS

	j	ra
	.end	mymalloc


#define FREE_SS				40

#define FREE_O_ARG 			(FREE_SS)

#define FREE_O_RA  			32
#define FREE_O_FP			28
#define FREE_O_GP			24

#define FREE_LTA_ARG		20	 
#define FREE_LTA_ACTUAL		16	 

	.globl	myfree
	.ent	myfree
myfree:
	subu	sp, sp, FREE_SS		

	sw		ra, FREE_O_RA(sp)			
	sw		$fp,FREE_O_FP(sp)
	sw		gp,FREE_O_GP(sp)						


	sw	a0, FREE_O_ARG(sp)  # Temporary: argument pointer.			
	sw	a0, FREE_LTA_ACTUAL(sp)  # Temporary: actual mmap(2) pointer.	
	move	$fp, sp

	# Calculate the actual mmap(2) pointer.
	#
	lw	t0, FREE_O_ARG(sp)			
	subu	t0, t0, 8
	sw	t0, FREE_LTA_ACTUAL(sp)		

	# XXX Sanity check: the argument pointer must be checked
	# in before we try to release the memory block.
	#
	# First, check the allocation signature.
	#
	lw	t0, FREE_LTA_ACTUAL(sp) # t0: actual mmap(2) pointer.
	lw	t1, 0(t0)
	bne	t1, MYMALLOC_SIGNATURE, myfree_die

	# Second, check the memory block trailer.
	#
	lw	t0, FREE_LTA_ACTUAL(sp) # t0: actual mmap(2) pointer. 	
	lw	t1, 4(t0)  # t1: actual mmap(2) block size.
	addu	t2, t0, t1 # t2: trailer pointer.
	lw	t3, -4(t2)
	xor	t3, t3, t1
	bne	t3, MYMALLOC_SIGNATURE, myfree_die

	# All checks passed. Try to free this memory area.
	#
	li	v0, SYS_munmap
	lw	a0, FREE_LTA_ACTUAL(sp) # a0: actual mmap(2) pointer.	
	lw	a1, 4(a0)  # a1: actual allocation size.
	syscall

	# Bail out if we cannot unmap this memory block.
	#
	bnez	v0, myfree_die

	# Success.
	#
	j myfree_return

myfree_die:
	# Generate a segmentation faul by writing to the first
	# byte of the adress space (a.ka. the NULL pointer).
	#
	sw t0, 0(zero)

myfree_return:
	# Destroy the stack frame.
	#
	move	sp, $fp
	lw	ra,  FREE_O_RA(sp)		
	lw	$fp, FREE_O_FP(sp)	
	addu	sp, sp,FREE_SS

	jr	ra
.end	myfree


# void *mymemcpy(void *dest, void *src, size_t n)
# 
#define MEMCPY_SS	8
#define MEMCPY_O_ARG	(MEMCPY_SS)
#define MEMCPY_O_FP	4
#define MEMCPY_O_GP	0

.globl	mymemcpy
.ent 	mymemcpy
mymemcpy:
	.frame $fp, MEMCPY_SS, ra
	.set  noreorder
   	.cpload  t9
   	.set  reorder

	subu	sp, sp, MEMCPY_SS
	sw	$fp, MEMCPY_O_FP(sp)
	sw	gp, MEMCPY_O_GP(sp)
	move	$fp, sp
	sw	a0, MEMCPY_O_ARG(sp)	# guardo dest en el stack frame del caller
	sw	a1, MEMCPY_O_ARG+4(sp)	# guardo src en el stack frame del caller
	sw	a2, MEMCPY_O_ARG+8(sp)	# guardo n en el stack frame del caller

	li	t0, 0			# cargo en t0 el valor cero.
	beqz 	a2, mymemcpy_return	# si n es cero termina la funcion.
	
iterar:
	addu	t1, t0, a1	# t1 = src + desplaz.
	addu	t2, t0, a0	# t2 = dest + desplaz.
	lb	t3, 0(t1)	# t3 = byte leido de t1.
	sb	t3, 0(t2)	# t3 = byte a escribir en t2.
	addiu	t0, t0, 1	# incremento t0.
	bne	t0, a2, iterar	# si t0 no es igual al n pasado como parametro sigo iterando.

	subu	t2, t2, t0
	addiu	t2, t2, 1
	move	v0, t2		# guardo como retorno en v0 la direccion del buffer dest.

mymemcpy_return:
	move	sp, $fp
	lw	$fp, MEMCPY_O_FP(sp)
	addiu	sp, sp, MEMCPY_SS
	jr	ra

.end mymemcpy

# myrealloc(void *, size_t) 
#define REALLOC_SS			48

#define REALLOC_O_ARG 			(REALLOC_SS)

#define REALLOC_O_RA  			40
#define REALLOC_O_FP			36
#define REALLOC_O_GP			32

#define REALLOC_LTA_NEW_DATA_PTR	28
#define REALLOC_LTA_OLD_BLOCK_SIZE	24	 
#define REALLOC_LTA_ACTUAL_MMAP_PTR	20
#define REALLOC_LTA_NEW_BLOCK_SIZE	16
.globl	myrealloc
.ent 	myrealloc
myrealloc:
	.frame $fp, REALLOC_SS, ra
	.set  noreorder
   	.cpload  t9
   	.set  reorder

	subu	sp, sp, REALLOC_SS
	sw	ra, REALLOC_O_RA(sp)			
	sw	$fp, REALLOC_O_FP(sp)
	sw	gp, REALLOC_O_GP(sp)

	sw	a0, REALLOC_O_ARG(sp)  	# Guardo el primer argumento en stack frame del caller
	sw	a1, REALLOC_O_ARG+4(sp)	# Guardo segundo argumento en stack frame del caller		
	sw	a0, REALLOC_LTA_ACTUAL_MMAP_PTR(sp)  # Temporary: actual mmap(2) pointer.
	sw	a1, REALLOC_LTA_NEW_BLOCK_SIZE(sp) # Temporary: new block size	
	move	$fp, sp

	# Calculate the actual mmap(2) pointer.
	#
	lw	t0, REALLOC_O_ARG(sp)			
	subu	t0, t0, 8
	sw	t0, REALLOC_LTA_ACTUAL_MMAP_PTR(sp)		

	# XXX Sanity check: the argument pointer must be checked
	# in before we try to release the memory block.
	#
	# First, check the allocation signature.
	#
	lw	t0, REALLOC_LTA_ACTUAL_MMAP_PTR(sp) # t0: actual mmap(2) pointer.
	lw	t1, 0(t0)
	bne	t1, MYMALLOC_SIGNATURE, myrealloc_die

	# Second, check the memory block trailer.
	#
	lw	t0, REALLOC_LTA_ACTUAL_MMAP_PTR(sp) # t0: actual mmap(2) pointer. 	
	lw	t1, 4(t0)  # t1: actual mmap(2) block size.
	sw	t1, REALLOC_LTA_OLD_BLOCK_SIZE(sp)
	addu	t2, t0, t1 # t2: trailer pointer.
	lw	t3, -4(t2)
	xor	t3, t3, t1
	bne	t3, MYMALLOC_SIGNATURE, myrealloc_die

	lw	a0, REALLOC_LTA_NEW_BLOCK_SIZE(sp)	# cargo el argumento para el malloc
	la	t9, mymalloc
	jalr	t9					# llamo al malloc
	sw	v0, REALLOC_LTA_NEW_DATA_PTR(sp) 	#guardo el puntero del malloc
	beqz	v0, myrealloc_return			#si no se puede asignar salgo

	lw	t0, REALLOC_LTA_NEW_BLOCK_SIZE(sp)
	lw	t1, REALLOC_LTA_OLD_BLOCK_SIZE(sp)
	lw	a2, REALLOC_LTA_OLD_BLOCK_SIZE(sp)
	bgeu	t0, t1, seguir
	lw	a2, REALLOC_LTA_NEW_BLOCK_SIZE(sp)
seguir:	lw	a0, REALLOC_LTA_NEW_DATA_PTR(sp)
	lw	a1, REALLOC_O_ARG(sp)
	la	t9, mymemcpy
	jalr	t9

	lw	a0, REALLOC_O_ARG(sp)
	la	t9, myfree
	jalr	t9
	
	j	myrealloc_return

myrealloc_die:
	# Generate a segmentation faul by writing to the first
	# byte of the adress space (a.ka. the NULL pointer).
	#
	sw t0, 0(zero)

myrealloc_return:
	# Valor de retorno
	lw 	v0, REALLOC_LTA_NEW_DATA_PTR(sp)

	# Destruyo el stack frame
	#
	move sp, $fp
	lw	ra,  REALLOC_O_RA(sp)		
	lw	$fp, REALLOC_O_FP(sp)	
	addu	sp, sp, REALLOC_SS
	j	ra
.end	myrealloc
	





